home *** CD-ROM | disk | FTP | other *** search
/ Almathera Ten Pack 3: CDPD 3 / Almathera Ten on Ten - Disc 3: CDPD3.iso / ab20 / ab20_archive / utilities / emulators / apple2emul.lzh / commands.c < prev    next >
C/C++ Source or Header  |  1991-04-18  |  14KB  |  881 lines

  1. /*
  2.  *  a2, an Apple II emulator in C
  3.  *  (c) Copyright 1990 by Rich Skrenta
  4.  *
  5.  *  Command line interface written by Tom Markson
  6.  *
  7.  *  Distribution agreement:
  8.  *
  9.  *    You may freely copy or redistribute this software, so long
  10.  *    as there is no profit made from its use, sale, trade or
  11.  *    reproduction.  You may not change this copyright notice,
  12.  *    and it must be included prominently in any copy made.
  13.  *
  14.  *  Send emulator related mail to:  skrenta@blekko.commodore.com
  15.  *                    skrenta@blekko.uucp
  16.  */
  17.  
  18.  
  19. #include    <stdio.h>
  20. #include    <ctype.h>
  21. #include    <signal.h>
  22. #include    "a2.h"
  23. #include    "cli.h"
  24.  
  25.  
  26. extern unsigned short lpoint;
  27. extern long phantom_location;
  28. extern char *getcwd();
  29. extern int map_to_upper;
  30. extern char escape_char;
  31. long get_hex_number();
  32.  
  33. char diskname[2][200];        /* disk names stored here */
  34.  
  35. struct point_stack {
  36.     unsigned short data[MAXSTACK];
  37.     int sp;
  38. } pstack;
  39.  
  40. init_point(){
  41.     pstack.sp = -1;
  42. }
  43.  
  44. pop_point(rest)
  45. char *rest;
  46. {
  47.     if (pstack.sp < 0 ) {
  48.         printf("stack empty\n");
  49.         return OK;
  50.     }
  51.     switch (*rest){
  52.         case 'l':
  53.         case 'L':
  54.             printf("%x\n", lpoint = pstack.data[pstack.sp--]);
  55.             break;
  56.         case 'p':
  57.         case 'P':
  58.             printf("%x\n", lpoint = pstack.data[pstack.sp--]);
  59.             break;
  60.         default : 
  61.             printf("pop [lp]\n");
  62.             break;
  63.     }
  64.     return(OK);
  65. }
  66.  
  67. dup_point(rest)
  68. char *rest;
  69. {
  70.     if (pstack.sp < 0 ) {
  71.         printf("stack empty\n");
  72.         return OK;
  73.     }
  74.     switch (*rest){
  75.         case 'l':
  76.         case 'L':
  77.             lpoint = pstack.data[pstack.sp];
  78.             break;
  79.         case 'p':
  80.         case 'P':
  81.             lpoint = pstack.data[pstack.sp];
  82.             break;
  83.         default : 
  84.             printf("dup [lp]\n");
  85.             break;
  86.     }
  87.     return(OK);
  88. }
  89.  
  90. push_point(rest)
  91. char *rest;
  92. {
  93.     long value;
  94.     char *addr;
  95.  
  96.     assert (pstack.sp < MAXSTACK);
  97.     switch (*rest){
  98.         case 'l':
  99.         case 'L':
  100.             pstack.data[++pstack.sp] = lpoint;
  101.             break;
  102.         case 'p':
  103.         case 'P':
  104.             pstack.data[++pstack.sp] = Pc;
  105.             break;
  106.         default:
  107.             addr = rest;
  108.             rest = split(rest);
  109.             value = get_hex_number(addr);
  110.             if (value == -1L) 
  111.                 printf("push [l|p|<addr>]\n");
  112.             else 
  113.                 pstack.data[++pstack.sp]=(unsigned short)value;
  114.             break;
  115.     }
  116.     return(OK);
  117. }
  118.  
  119.  
  120. clb(junk)
  121. char *junk;
  122. {
  123.  
  124.     B = 0;
  125.     return(DISPLAY);
  126. }
  127.  
  128.  
  129. seb(junk)
  130. char *junk;
  131. {
  132.  
  133.     B = 1;
  134.     return(DISPLAY);
  135. }
  136.  
  137.  
  138. clc(junk)
  139. char    *junk;
  140. {
  141.     C = 0;
  142.     return(DISPLAY);
  143. }
  144.  
  145.  
  146. sec(junk)
  147. char    *junk;
  148. {
  149.     C = 1;
  150.     return(DISPLAY);
  151. }
  152.  
  153.  
  154. sev(junk)
  155. char    *junk;
  156. {
  157.     V = 1;
  158.     return(DISPLAY);
  159. }
  160.  
  161.  
  162. clv(junk)
  163. char    *junk;
  164. {
  165.     V = 0;
  166.     return(DISPLAY);
  167. }
  168.  
  169.  
  170. sed(junk)
  171. char    *junk;
  172. {
  173.     D = 1;
  174.     return(DISPLAY);
  175. }
  176.  
  177.  
  178. cld(junk)
  179. char    *junk;
  180. {
  181.     D = 0;
  182.     return(DISPLAY);
  183. }
  184.  
  185.  
  186. sei(junk)
  187. char    *junk;
  188. {
  189.     I = 1;
  190.     return(DISPLAY);
  191. }
  192.  
  193.  
  194. clri(junk)
  195. char    *junk;
  196. {
  197.     I = 0;
  198.     return(DISPLAY);
  199. }
  200.  
  201.  
  202. sen(junk)
  203. char    *junk;
  204. {
  205.     N = 1;
  206.     return(DISPLAY);
  207. }
  208.  
  209.  
  210. cln(junk)
  211. char    *junk;
  212. {
  213.     N = 0;
  214.     return(DISPLAY);
  215. }
  216.  
  217.  
  218. sez(junk)
  219. char    *junk;
  220. {
  221.     NZ = 0;
  222.     return(DISPLAY);
  223. }
  224.  
  225.  
  226. clz(junk)
  227. char    *junk;
  228. {
  229.     NZ = 1;
  230.     return(DISPLAY);
  231. }
  232.  
  233.  
  234. quit_emulator(junk)
  235. char    *junk;
  236. {
  237.     exit(0);
  238. }
  239.  
  240.  
  241.  
  242. ver(junk)
  243. char    *junk;
  244. {
  245.     printf("a2 - Apple II emulator  (c) Copyright 1990 by Rich Skrenta & Tom Markson\n");
  246.     return(OK);
  247. }
  248.  
  249.  
  250. refresh(junk)
  251. char    *junk;
  252. {
  253.  
  254.     in_cli = FALSE;
  255.     return(OK);
  256. }
  257.  
  258.  
  259. shell_escape(rest)
  260. char    *rest;
  261. {
  262.     char    line[100];
  263.     char *s;
  264.  
  265.     s = getenv("SHELL");
  266.     if (s == NULL)
  267.         s = "/bin/sh";
  268.  
  269.     strcpy(line, s);
  270.  
  271.     if (*rest != '\0') {
  272.         strcat(line, " -c '");
  273.         strcat(line, rest);
  274.         strcat(line, "'");
  275.     }
  276.     system(line);
  277.  
  278.     printf("\n");
  279.     return(OK);
  280. }
  281.  
  282.  
  283. do_soft_reset(rest)
  284. char    *rest;
  285. {
  286.  
  287.     Pc = mem[0xFFFC] | (mem[0xFFFD] << 8);
  288.     return(DISPLAY);
  289. }
  290.  
  291.  
  292. do_bload(rest)
  293. char    *rest;
  294. {
  295.     char    *first;
  296.     char    *file;
  297.     unsigned short    start;
  298.     long    foo;
  299.  
  300.     file = rest;
  301.     rest = split(rest);
  302.     first = rest;
  303.     rest = split(rest);
  304.     foo = get_hex_number(first);
  305.     if (foo == -1) {
  306.         printf("usage: bload file hex-address\n");
  307.         return(OK);
  308.     }
  309.     start = (unsigned int) foo;
  310.     bload(file, start);
  311.  
  312.     return(OK);
  313. }
  314.  
  315.  
  316. do_bsave(rest)
  317. char    *rest;
  318. {
  319.     char    *startc, *sizec, *file;
  320.     unsigned short    start, size;
  321.     long    istart, iend;
  322.     file = rest;
  323.     rest = split(rest);
  324.     startc = rest;
  325.     rest = split(rest);
  326.     sizec = rest;
  327.     rest = split(rest);
  328.     istart = get_hex_number(startc);
  329.     iend = get_hex_number(sizec);
  330.     if ((istart == -1) || (iend == -1))
  331.         printf("usage: bsave file hex-address hex-length\n");
  332.     else {
  333.         start = (unsigned short) istart;
  334.         size  = (unsigned short) iend;
  335.         bsave(file, start, size);
  336.     }
  337.  
  338.     return(OK);
  339. }
  340.  
  341.  
  342.  
  343. show_point(rest)
  344. char    *rest;
  345. {
  346.  
  347.     lpoint = Pc;
  348.     return(DISPLAY);
  349. }
  350.  
  351.  
  352. hack(rest)
  353. char *rest;
  354. {
  355. extern int cur_track;
  356.  
  357.     cur_track = get_hex_number(rest);
  358.     return(OK);
  359. }
  360.  
  361.  
  362. do_jump(rest)
  363. char    *rest;
  364. {
  365.     char    *start;
  366.     long    istart;
  367.     start = rest;
  368.     rest = split(rest);
  369.     istart = get_hex_number(start);
  370.  
  371.     if (istart == -1) {
  372.         printf("usage: jmp <hex address>\n");
  373.         return(OK);
  374.     } else {
  375.         Pc = istart & 0xFFFF;
  376.         return(DISPLAY);
  377.     }
  378. }
  379.  
  380.  
  381.  
  382. trace(rest)
  383. char    *rest;
  384. {
  385.     char    *addr1, *addr2, *file;
  386.     long    addr1i, addr2i;
  387.  
  388.     addr1 = rest;
  389.     rest = split(rest);
  390.     addr2 = rest;
  391.     rest = split(rest);
  392.     file = rest;
  393.     rest = split(rest);
  394.     addr1i = get_hex_number(addr1);
  395.     addr2i = get_hex_number(addr2);
  396.     if (addr1i == -1 && addr2i == -1) {
  397.         if (trace_lo == -1)
  398.             printf("No trace region set\n");
  399.         else
  400.             printf("Tracing between $%.4X and $%.4x\n",
  401.                             trace_lo, trace_hi);
  402.         return(OK);
  403.     }
  404.  
  405.     if (addr1i == -1 || addr2i == -1) {
  406.         printf("usage: trace [low high]\n");
  407.         return(OK);
  408.     }
  409.  
  410.     if (logging_fp == NULL) {
  411.         if (*file == '\0' || file == NULL) {
  412.             printf("Trace log will go to file 'trace'.\n");
  413.             file = "trace";
  414.         }
  415.         logging_fp = fopen(file, "w");
  416.         if (logging_fp == NULL) {
  417.             perror("can't open trace file");
  418.             trace_lo = -1;
  419.             return(OK);
  420.         }
  421.     }
  422.  
  423.     trace_lo = addr1i & 0xFFFF;
  424.     trace_hi = addr2i & 0xFFFF;
  425.  
  426.     return(OK);
  427. }
  428.  
  429.  
  430. ldx(rest)
  431. char    *rest;
  432. {
  433.     long    number;
  434.     char    *first;
  435.  
  436.     first = rest;
  437.     rest = split(rest);
  438.     number = get_hex_number(first);
  439.  
  440.     number &= 0xFF;
  441.     if (number < 0) {
  442.         printf("usage: ldx <hex number>\n");
  443.         return(OK);
  444.     }
  445.  
  446.     X = number & 0xFF;
  447.     return(DISPLAY);
  448. }
  449.  
  450.  
  451. ldy(rest)
  452. char    *rest;
  453. {
  454.     long    number;
  455.     char    *first;
  456.     first = rest;
  457.     rest = split(rest);
  458.     number = get_hex_number(first);
  459.  
  460.     if (number < 0) {
  461.         printf("usage: ldy <hex number>\n");
  462.         return(OK);
  463.     }
  464.  
  465.     Y = number & 0xFF;
  466.     return(DISPLAY);
  467. }
  468.  
  469.  
  470. lda(rest)
  471. char    *rest;
  472. {
  473.     long    number;
  474.     char    *first;
  475.     first = rest;
  476.     rest = split(rest);
  477.     number = get_hex_number(first);
  478.     if (number < 0) {
  479.         printf("usage: lda <hex number>\n");
  480.         return(OK);
  481.     }
  482.  
  483.     A = number & 0xFF;
  484.     return(DISPLAY);
  485. }
  486.  
  487.  
  488. lds(rest)
  489. char    *rest;
  490. {
  491.     long    number;
  492.     char    *first;
  493.     first = rest;
  494.     rest = split(rest);
  495.     number = get_hex_number(first);
  496.     if (number < 0) {
  497.         printf("usage: lds <hex number>\n");
  498.         return(OK);
  499.     }
  500.  
  501.     Sp = number & 0xFF;
  502.     return(DISPLAY);
  503. }
  504.  
  505.  
  506. set_break_point(rest)
  507. char    *rest;
  508. {    
  509.     long    addr;
  510.     char    *first;
  511.     first = rest;
  512.     rest = split(rest);
  513.     addr = get_hex_number(first);
  514.     if (addr == -1)
  515.         if (breakpoint == -1)
  516.             printf("no breakpoint set\n");
  517.             else
  518.             printf("break point set at %x\n",
  519.                 (unsigned short)breakpoint);
  520.             else
  521.         breakpoint = addr;
  522.     running = FALSE;
  523.     return(OK);
  524. }
  525.  
  526.  
  527. clear_break_point(rest)
  528. char    *rest;
  529. {
  530.     breakpoint = -1;
  531.     return(OK);
  532. }
  533.  
  534.  
  535. notrace(junk)
  536. char    *junk;
  537. {
  538.  
  539.     trace_lo = -1;
  540.     if (logging_fp == NULL)
  541.         return(OK);
  542.     else
  543.         fclose(logging_fp);
  544.     logging_fp = NULL;
  545.     return(OK);
  546. }
  547.  
  548.  
  549. insert_disk(rest)
  550. char    *rest;
  551. {
  552.     char    *name;
  553.     char    *which;
  554.     int    fd;
  555.     int    read_only = 0;
  556.  
  557.     name = rest;
  558.     rest = split(rest);
  559.     which = rest;
  560.     rest = split(rest);
  561.  
  562.     if (name == NULL || *name == '\0') {
  563.         printf("usage: insert <file name> [drive]\n");
  564.         return(OK);
  565.     }
  566.  
  567.     fd = open(name, 2);        /* attempt open for read/write */
  568.     if (fd >= 0)
  569.         read_only = 0;
  570.     else  {                /* attempt open read only */
  571.         read_only = 1;
  572.         fd = open(name, 0);
  573.     }
  574.     if (fd < 0) {
  575.         fprintf(stderr, "can't open %s: ", name);
  576.         perror("");
  577.         return(OK);
  578.     }
  579.  
  580.     if (*which == '2')
  581.         drive = 1;
  582.     else
  583.         drive = 0;
  584.  
  585.     if (disk[drive] >= 0)
  586.         close(disk[drive]);
  587.  
  588.     strcpy(diskname[drive], name);
  589.  
  590.     disk[drive] = fd;
  591.     write_prot[drive] = read_only;
  592.  
  593.     printf("disk %d inserted, %swrite protected\n", drive + 1,
  594.                 read_only ? "" : "not ");
  595.  
  596.     return(OK);
  597. }
  598.  
  599.  
  600. dissassemble(rest)
  601. char *rest;
  602. {
  603.     unsigned short start,end;
  604.     long istart;
  605.     long iend;
  606.     int count = 0;
  607.     char *first,*last;
  608.  
  609.     first = rest;
  610.     rest = split(rest);
  611.     last = rest;
  612.     rest = split(rest);
  613.  
  614.     istart = get_hex_number(first);
  615.     iend   = get_hex_number(last);
  616.     if (istart != -1)
  617.         lpoint = istart;
  618.     if (iend == -1)
  619.         iend=65537;
  620.     while ( (long) lpoint < iend) {
  621.         lpoint += diss(lpoint, stdout);
  622.         printf("\n");
  623.         count++;
  624.         if (iend == 65537)
  625.             if (count > term_lines-3)
  626.                 break;
  627.     }
  628.     return OK;
  629. }
  630.  
  631. ascii_dump(l,h)
  632. unsigned short l,h;
  633. {
  634.     while (l < h) {
  635.         if (isprint(mem[l]))
  636.             printf("%c",mem[l]);
  637.         else 
  638.             printf(".");
  639.         l++;
  640.     }
  641. }
  642.  
  643. hex_dump(rest)
  644. char *rest;
  645. {
  646.     char *start,*end;
  647.     unsigned short last, addr,oaddr;
  648.     long iaddr1,iaddr2;
  649.     int count;
  650.  
  651.     start = rest;
  652.     rest = split(rest);
  653.     end = rest;
  654.     rest = split(rest);
  655.     iaddr1 = get_hex_number( start );
  656.     iaddr2 = get_hex_number( end );
  657.  
  658.     if (iaddr2 != -1 && iaddr1 > iaddr2)
  659.         return(OK);
  660.  
  661.     if (iaddr1 != -1)
  662.         lpoint = (unsigned short) iaddr1;
  663.     if (iaddr2 == -1)
  664.         last = lpoint + 1;
  665.     else
  666.         last = (unsigned short) iaddr2 + 1;
  667.  
  668.     last &= 0xFFFF;
  669.  
  670.     addr = lpoint;
  671.     count = 0;
  672.     printf("%.4X:  ", addr);
  673.     oaddr = addr;
  674.     do {
  675.         if (count % 16 == 0 && count != 0) {
  676.             ascii_dump(oaddr,addr);
  677.             oaddr = addr;
  678.             printf("\n%.4X:  ", addr);
  679.         }
  680.         printf("%.2X ", mem[addr]);
  681.         addr++;
  682.         count++;
  683.     } while (addr != last);
  684.     while ((count % 16) != 0) {
  685.         printf("   ");  /* 3 spaces dd_ */
  686.         count++;
  687.     }
  688.     ascii_dump(oaddr,addr);
  689.     printf("\n");
  690.     return(OK);
  691. }
  692.  
  693. deposit(rest)
  694. char *rest;
  695. {
  696.     char *addr;
  697.     char *value;
  698.     unsigned short location;
  699.     long iloc;
  700.     unsigned char val;
  701.     int fired_once;
  702.  
  703.     addr = rest;
  704.     rest = split(rest);
  705.     fired_once = 0;
  706.     iloc = get_hex_number(addr);
  707.     if (iloc == -1) {
  708.         printf("usage: deposit <addr> <value> [<value>...]\n");
  709.         return(OK);
  710.     }
  711.  
  712.     location = (unsigned short) iloc;
  713.     do {
  714.         value = rest;
  715.         rest = split(rest);
  716.         val = get_hex_number(value);
  717.         if (val == -1) {
  718.             if (!fired_once)
  719.                 printf("Invalid or Missing Hex address\n");
  720.             return OK;
  721.         }
  722.         mem[location++] = val;
  723.         fired_once = 1;
  724.     } while (*rest != '\0');
  725.  
  726.     return(OK);
  727. }
  728.  
  729. phantom_trace(rest)
  730. char *rest;
  731. {
  732.     char *phantoms;
  733.     char *addr1s;
  734.     char *addr2s;
  735.     char *file;
  736.     long  phantom;
  737.     long low_val,high_val;
  738.     int err = 0;
  739.  
  740.     phantoms = rest;
  741.     rest = split(rest);
  742.     addr1s = rest;
  743.     rest = split(rest);
  744.     addr2s = rest;
  745.     rest = split(rest);
  746.     file = rest;
  747.     rest = split(rest);
  748.  
  749.     phantom = (unsigned short)get_hex_number(phantoms);
  750.     low_val = get_hex_number(addr1s);
  751.     high_val = get_hex_number(addr2s);
  752.     if (*phantoms == '\0') {
  753.         if (phantom_location == -1) {
  754.             printf("The phantom sleeps.");
  755.             if (trace_lo != -1 && trace_hi != -1)
  756.                 printf("however, a trace is active.");
  757.             printf("\n");
  758.         } else 
  759.         printf("the phantom waits until Pc = %.4X and then traces from %.4X to %.4X\n",phantom_location,trace_lo,trace_hi);
  760.         return(OK);
  761.     }
  762.     if (low_val == -1 || high_val == -1 || phantom == -1) {
  763.         printf("usage: phantom <addr> <addr> <addr> [file]\n");
  764.         return OK;
  765.     } phantom_location = phantom; trace_lo = low_val; trace_hi = high_val; if (logging_fp == NULL) {
  766.         if (*file == '\0' || file == NULL) {
  767.             printf("the phantom will trace to file 'trace'.\n");
  768.             file = "trace";
  769.         }
  770.         logging_fp = fopen(file, "w");
  771.         if (logging_fp == NULL) {
  772.             perror("can't open trace file");
  773.             trace_lo = -1;
  774.             return(OK);
  775.         }
  776.     }
  777.     return OK;
  778. }
  779.  
  780. no_phantom_trace(rest)
  781. char *rest;
  782. {
  783.     phantom_location = -1;
  784.     trace_lo = -1;
  785.     printf("the phantom goes to sleep.\n");
  786.     if (logging_fp == NULL)
  787.         return OK;
  788.     fclose(logging_fp);
  789.     logging_fp = NULL;
  790.     return OK;
  791. }
  792.  
  793. cd(rest)
  794. char *rest;
  795. {
  796.  
  797.     char *first;
  798.     char path[200];
  799.  
  800.     first = rest;
  801.     rest = split(rest);
  802.     if (*first != '\0') {
  803.         if (chdir(first)) {
  804.             perror("cd");
  805.             printf("CWD remains ");
  806.         }
  807.     }
  808.     printf("%s\n",getcwd(path,198));
  809.     return OK;
  810. }
  811.  
  812. map(rest)
  813. char *rest;
  814. {
  815.     map_to_upper = !map_to_upper;
  816.     printf("Uppercase Mapping is %s\n",(map_to_upper)?"On":"Off");
  817.     return OK;
  818. }
  819.  
  820. sex(rest)
  821. char *rest;
  822. {
  823.     printf("You'll need a real Apple for that sort of thing\n");
  824.     return OK;
  825. }
  826.  
  827.  
  828. help(rest)
  829. char *rest;
  830. {
  831.     printf("![command]        Escape to Unix\n");
  832.     printf(".            Display Current Pc Point\n");
  833.     printf("bload file addr        load binary file into mem at addr\n");
  834.     printf("breakpoint [addr]    Set the Breakpoint to addr\n");
  835.     printf("bsave file start end    Save Memory from start to end in file\n");
  836.     printf("cd [directory]        Set/Show Current Working Directory\n");
  837.     printf("cl[cdinvz]        Clear appropriate Status Bit\n");
  838.     printf("continue        Resume Execution of Emulator\n");
  839.     printf("deposit addr [val]+    Put val(s) into addr\n");
  840.     printf("dup [l|p]        duplicate top of stack into l or p\n");
  841.     printf("escape char        set escape char to be char \n");
  842.     printf("examine addr        Display Value in addr\n");
  843.     printf("insert file drive#    Make file disk in drive#\n");
  844.     printf("jmp addr        Make Pc=addr\n");
  845.     printf("ld[asxy] val        Load Register with val\n");
  846.     printf("list [addr] [addr]    Dissassemble at point or addr\n");
  847.     printf("map            Toggle lower -> upper case mapping\n");
  848.     printf("nobreak            Turn off breakpoint\n");
  849.     printf("pop [l|p]        get p or l from top of stack\n");
  850.     printf("push [l|p|<addr>    push l,p, or hex addr on stack\n");
  851.     printf("reset            Pc = Apple Reset Vector\n");
  852.     printf("se[cdinvz]        Set appropriate Status Flag\n");
  853.     printf("trace [addr] [addr]    Trace Between addresses/display trace point\n");
  854.     return OK;
  855. }
  856.  
  857. set_escape_char(rest)
  858. char *rest;
  859. {
  860.     char c;
  861.     if (*rest != '\0')
  862.         escape_char = *rest;
  863.     printf("escape character is ");
  864.     if (isprint(escape_char))
  865.         printf("%c",(int)escape_char);
  866.     else 
  867.         printf("^%c",(char)escape_char+64);
  868.     printf(" (0x%.2X)",(int)escape_char);
  869.     printf("\n");
  870.     return(OK);
  871. }
  872.  
  873. disk_names(rest)
  874. char *rest;
  875. {
  876.  
  877.     printf("drive 1: %s\n", disk[0] >= 0 ? diskname[0] : "empty");
  878.     printf("drive 2: %s\n", disk[1] >= 0 ? diskname[1] : "empty");
  879.     return(OK);
  880. }
  881.